// Decompiled on 周六 2月 22 19:31:49 CST 2025 with Zomboid Decompiler v0.1.3 using Vineflower.
package zombie.popman;

import com.google.common.collect.Sets;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.HashSet;
import zombie.ai.states.ZombieHitReactionState;
import zombie.ai.states.ZombieOnGroundState;
import zombie.characters.IsoPlayer;
import zombie.characters.IsoZombie;
import zombie.characters.NetworkZombieVariables;
import zombie.core.Core;
import zombie.core.network.ByteBufferWriter;
import zombie.core.raknet.UdpConnection;
import zombie.core.utils.UpdateLimit;
import zombie.debug.DebugLog;
import zombie.debug.DebugOptions;
import zombie.debug.DebugType;
import zombie.iso.IsoDirections;
import zombie.iso.IsoGridSquare;
import zombie.iso.IsoWorld;
import zombie.iso.objects.IsoDeadBody;
import zombie.network.GameClient;
import zombie.network.PacketTypes;
import zombie.network.packets.ZombiePacket;

public class NetworkZombieSimulator {
    public static final int MAX_ZOMBIES_PER_UPDATE = 300;
    private static final NetworkZombieSimulator instance = new NetworkZombieSimulator();
    private static final ZombiePacket zombiePacket = new ZombiePacket();
    private final ByteBuffer bb = ByteBuffer.allocate(1000000);
    private final ArrayList<Short> unknownZombies = new ArrayList();
    private final HashSet<Short> authoriseZombies = new HashSet();
    private final ArrayDeque<IsoZombie> SendQueue = new ArrayDeque();
    private final ArrayDeque<IsoZombie> ExtraSendQueue = new ArrayDeque();
    private HashSet<Short> authoriseZombiesCurrent = new HashSet();
    private HashSet<Short> authoriseZombiesLast = new HashSet();
    UpdateLimit ZombieSimulationReliableLimit = new UpdateLimit(1000L);

    public static NetworkZombieSimulator getInstance() {
        return instance;
    }

    public int getAuthorizedZombieCount() {
        return (int)IsoWorld.instance.CurrentCell.getZombieList().stream().filter(isoZombie -> isoZombie.authOwner == GameClient.connection).count();
    }

    public int getUnauthorizedZombieCount() {
        return (int)IsoWorld.instance.CurrentCell.getZombieList().stream().filter(isoZombie -> isoZombie.authOwner == null).count();
    }

    public void clear() {
        HashSet hashSet = this.authoriseZombiesCurrent;
        this.authoriseZombiesCurrent = this.authoriseZombiesLast;
        this.authoriseZombiesLast = hashSet;
        this.authoriseZombiesLast.removeIf(_short -> GameClient.getZombie(_short) == null);
        this.authoriseZombiesCurrent.clear();
    }

    public void addExtraUpdate(IsoZombie zombie) {
        if (zombie.authOwner == GameClient.connection && !this.ExtraSendQueue.contains(zombie)) {
            this.ExtraSendQueue.add(zombie);
        }
    }

    public void add(short onlineId) {
        this.authoriseZombiesCurrent.add(onlineId);
    }

    public void added() {
        for (Short short0 : Sets.difference(this.authoriseZombiesCurrent, this.authoriseZombiesLast)) {
            IsoZombie isoZombie0 = GameClient.getZombie(short0);
            if (isoZombie0 != null && isoZombie0.OnlineID == short0) {
                this.becomeLocal(isoZombie0);
            } else if (!this.unknownZombies.contains(short0)) {
                this.unknownZombies.add(short0);
            }
        }

        for (Short short1 : Sets.difference(this.authoriseZombiesLast, this.authoriseZombiesCurrent)) {
            IsoZombie isoZombie1 = GameClient.getZombie(short1);
            if (isoZombie1 != null) {
                this.becomeRemote(isoZombie1);
            }
        }

        synchronized (this.authoriseZombies) {
            this.authoriseZombies.clear();
            this.authoriseZombies.addAll(this.authoriseZombiesCurrent);
        }
    }

    public void becomeLocal(IsoZombie z) {
        z.lastRemoteUpdate = 0;
        z.authOwner = GameClient.connection;
        z.authOwnerPlayer = IsoPlayer.getInstance();
        z.networkAI.setUpdateTimer(0.0F);
        z.AllowRepathDelay = 0.0F;
        z.networkAI.mindSync.restorePFBTarget();
    }

    public void becomeRemote(IsoZombie z) {
        if (z.isDead() && z.authOwner == GameClient.connection) {
            z.getNetworkCharacterAI().setLocal(true);
        }

        z.lastRemoteUpdate = 0;
        z.authOwner = null;
        z.authOwnerPlayer = null;
        if (z.group != null) {
            z.group.remove(z);
        }
    }

    public boolean isZombieSimulated(Short zombieId) {
        synchronized (this.authoriseZombies) {
            return this.authoriseZombies.contains(zombieId);
        }
    }

    public void receivePacket(ByteBuffer b, UdpConnection connection) {
        if (DebugOptions.instance.Network.Client.UpdateZombiesFromPacket.getValue()) {
            short short0 = b.getShort();

            for (short short1 = 0; short1 < short0; short1++) {
                this.parseZombie(b, connection);
            }
        }
    }

    private void parseZombie(ByteBuffer byteBuffer, UdpConnection udpConnection) {
        ZombiePacket zombiePacketx = zombiePacket;
        zombiePacketx.parse(byteBuffer, udpConnection);
        if (zombiePacketx.id == -1) {
            DebugLog.General.error("NetworkZombieSimulator.parseZombie id=" + zombiePacketx.id);
        } else {
            try {
                IsoZombie isoZombie = GameClient.IDToZombieMap.get(zombiePacketx.id);
                if (isoZombie == null) {
                    if (IsoDeadBody.isDead(zombiePacketx.id)) {
                        DebugLog.Death.debugln("Skip dead zombie creation id=%d", zombiePacketx.id);
                        return;
                    }

                    IsoGridSquare isoGridSquare = IsoWorld.instance
                        .CurrentCell
                        .getGridSquare((double)zombiePacketx.realX, (double)zombiePacketx.realY, (double)zombiePacketx.realZ);
                    if (isoGridSquare != null) {
                        zombie.VirtualZombieManager.instance.choices.clear();
                        zombie.VirtualZombieManager.instance.choices.add(isoGridSquare);
                        isoZombie = zombie.VirtualZombieManager.instance
                            .createRealZombieAlways(zombiePacketx.descriptorID, IsoDirections.getRandom().index(), false);
                        DebugLog.log(DebugType.ActionSystem, "ParseZombie: CreateRealZombieAlways id=" + zombiePacketx.id);
                        if (isoZombie != null) {
                            isoZombie.setFakeDead(false);
                            isoZombie.OnlineID = zombiePacketx.id;
                            GameClient.IDToZombieMap.put(zombiePacketx.id, isoZombie);
                            isoZombie.lx = isoZombie.nx = isoZombie.x = zombiePacketx.realX;
                            isoZombie.ly = isoZombie.ny = isoZombie.y = zombiePacketx.realY;
                            isoZombie.lz = isoZombie.z = (float)zombiePacketx.realZ;
                            isoZombie.setForwardDirection(isoZombie.dir.ToVector());
                            isoZombie.setCurrent(isoGridSquare);
                            isoZombie.networkAI.targetX = zombiePacketx.x;
                            isoZombie.networkAI.targetY = zombiePacketx.y;
                            isoZombie.networkAI.targetZ = zombiePacketx.z;
                            isoZombie.networkAI.predictionType = zombiePacketx.moveType;
                            isoZombie.networkAI.reanimatedBodyID = zombiePacketx.reanimatedBodyID;
                            NetworkZombieVariables.setInt(isoZombie, (short)0, zombiePacketx.realHealth);
                            NetworkZombieVariables.setInt(isoZombie, (short)2, zombiePacketx.speedMod);
                            NetworkZombieVariables.setInt(isoZombie, (short)1, zombiePacketx.target);
                            NetworkZombieVariables.setInt(isoZombie, (short)3, zombiePacketx.timeSinceSeenFlesh);
                            NetworkZombieVariables.setInt(isoZombie, (short)4, zombiePacketx.smParamTargetAngle);
                            NetworkZombieVariables.setBooleanVariables(isoZombie, zombiePacketx.booleanVariables);
                            if (isoZombie.isKnockedDown()) {
                                isoZombie.setOnFloor(true);
                                isoZombie.changeState(ZombieOnGroundState.instance());
                            }

                            isoZombie.setWalkType(zombiePacketx.walkType.toString());
                            isoZombie.realState = zombiePacketx.realState;
                            if (isoZombie.isReanimatedPlayer()) {
                                IsoDeadBody isoDeadBody = IsoDeadBody.getDeadBody(isoZombie.networkAI.reanimatedBodyID);
                                if (isoDeadBody != null) {
                                    isoZombie.setDir(isoDeadBody.getDir());
                                    isoZombie.setForwardDirection(isoDeadBody.getDir().ToVector());
                                    isoZombie.setFallOnFront(isoDeadBody.isFallOnFront());
                                }

                                isoZombie.getStateMachine().changeState(ZombieOnGroundState.instance(), null);
                                isoZombie.bNeverDoneAlpha = false;
                            }

                            for (int _int = 0; _int < IsoPlayer.numPlayers; _int++) {
                                IsoPlayer isoPlayer = IsoPlayer.players[_int];
                                if (isoGridSquare.isCanSee(_int)) {
                                    isoZombie.setAlphaAndTarget(_int, 1.0F);
                                }

                                if (isoPlayer != null && isoPlayer.ReanimatedCorpseID == zombiePacketx.id && zombiePacketx.id != -1) {
                                    isoPlayer.ReanimatedCorpseID = -1;
                                    isoPlayer.ReanimatedCorpse = isoZombie;
                                }
                            }

                            isoZombie.networkAI.mindSync.parse(zombiePacketx);
                        } else {
                            DebugLog.log("Error: VirtualZombieManager can't create zombie");
                        }
                    }

                    if (isoZombie == null) {
                        return;
                    }
                }

                if (getInstance().isZombieSimulated(isoZombie.OnlineID)) {
                    isoZombie.authOwner = GameClient.connection;
                    isoZombie.authOwnerPlayer = IsoPlayer.getInstance();
                    return;
                }

                isoZombie.authOwner = null;
                isoZombie.authOwnerPlayer = null;
                if (!isoZombie.networkAI.isSetVehicleHit() || !isoZombie.isCurrentState(ZombieHitReactionState.instance())) {
                    isoZombie.networkAI.parse(zombiePacketx);
                    isoZombie.networkAI.mindSync.parse(zombiePacketx);
                }

                isoZombie.lastRemoteUpdate = 0;
                if (!IsoWorld.instance.CurrentCell.getZombieList().contains(isoZombie)) {
                    IsoWorld.instance.CurrentCell.getZombieList().add(isoZombie);
                }

                if (!IsoWorld.instance.CurrentCell.getObjectList().contains(isoZombie)) {
                    IsoWorld.instance.CurrentCell.getObjectList().add(isoZombie);
                }
            } catch (Exception exception) {
                exception.printStackTrace();
            }
        }
    }

    public boolean anyUnknownZombies() {
        return this.unknownZombies.size() > 0;
    }

    public void send() {
        if (this.authoriseZombies.size() != 0 || this.unknownZombies.size() != 0) {
            if (this.SendQueue.isEmpty()) {
                synchronized (this.authoriseZombies) {
                    for (Short _short : this.authoriseZombies) {
                        IsoZombie isoZombie0 = GameClient.getZombie(_short);
                        if (isoZombie0 != null && isoZombie0.OnlineID != -1) {
                            this.SendQueue.add(isoZombie0);
                        }
                    }
                }
            }

            this.bb.clear();
            synchronized (ZombieCountOptimiser.zombiesForDelete) {
                int int0 = ZombieCountOptimiser.zombiesForDelete.size();
                this.bb.putShort((short)int0);

                for (int int1 = 0; int1 < int0; int1++) {
                    this.bb.putShort(((IsoZombie)ZombieCountOptimiser.zombiesForDelete.get(int1)).OnlineID);
                }

                ZombieCountOptimiser.zombiesForDelete.clear();
            }

            int int2 = this.unknownZombies.size();
            this.bb.putShort((short)int2);

            for (int int3 = 0; int3 < int2; int3++) {
                this.bb.putShort((Short)this.unknownZombies.get(int3));
            }

            this.unknownZombies.clear();
            int int4 = this.bb.position();
            this.bb.putShort((short)300);
            int int5 = 0;

            while (!this.SendQueue.isEmpty()) {
                IsoZombie isoZombie1 = (IsoZombie)this.SendQueue.poll();
                this.ExtraSendQueue.remove(isoZombie1);
                isoZombie1.zombiePacket.set(isoZombie1);
                if (isoZombie1.OnlineID != -1) {
                    isoZombie1.zombiePacket.write(this.bb);
                    isoZombie1.networkAI.targetX = isoZombie1.realx = isoZombie1.x;
                    isoZombie1.networkAI.targetY = isoZombie1.realy = isoZombie1.y;
                    isoZombie1.networkAI.targetZ = isoZombie1.realz = (byte)((int)isoZombie1.z);
                    isoZombie1.realdir = isoZombie1.getDir();
                    if (++int5 >= 300) {
                        break;
                    }
                }
            }

            if (int5 < 300) {
                int int6 = this.bb.position();
                this.bb.position(int4);
                this.bb.putShort((short)int5);
                this.bb.position(int6);
            }

            if (int5 > 0 || int2 > 0) {
                ByteBufferWriter byteBufferWriter0 = GameClient.connection.startPacket();
                PacketTypes.PacketType packetType;
                if (int2 > 0 && this.ZombieSimulationReliableLimit.Check()) {
                    packetType = PacketTypes.PacketType.ZombieSimulationReliable;
                } else {
                    packetType = PacketTypes.PacketType.ZombieSimulation;
                }

                packetType.doPacket(byteBufferWriter0);
                byteBufferWriter0.bb.put(this.bb.array(), 0, this.bb.position());
                packetType.send(GameClient.connection);
            }

            if (!this.ExtraSendQueue.isEmpty()) {
                this.bb.clear();
                this.bb.putShort((short)0);
                this.bb.putShort((short)0);
                int4 = this.bb.position();
                this.bb.putShort((short)0);
                int int7 = 0;

                while (!this.ExtraSendQueue.isEmpty()) {
                    IsoZombie isoZombie2 = (IsoZombie)this.ExtraSendQueue.poll();
                    isoZombie2.zombiePacket.set(isoZombie2);
                    if (isoZombie2.OnlineID != -1) {
                        isoZombie2.zombiePacket.write(this.bb);
                        isoZombie2.networkAI.targetX = isoZombie2.realx = isoZombie2.x;
                        isoZombie2.networkAI.targetY = isoZombie2.realy = isoZombie2.y;
                        isoZombie2.networkAI.targetZ = isoZombie2.realz = (byte)((int)isoZombie2.z);
                        isoZombie2.realdir = isoZombie2.getDir();
                        int7++;
                    }
                }

                int int8 = this.bb.position();
                this.bb.position(int4);
                this.bb.putShort((short)int7);
                this.bb.position(int8);
                if (int7 > 0) {
                    ByteBufferWriter byteBufferWriter1 = GameClient.connection.startPacket();
                    PacketTypes.PacketType.ZombieSimulation.doPacket(byteBufferWriter1);
                    byteBufferWriter1.bb.put(this.bb.array(), 0, this.bb.position());
                    PacketTypes.PacketType.ZombieSimulation.send(GameClient.connection);
                }
            }
        }
    }

    public void remove(IsoZombie zombie) {
        if (zombie != null && zombie.OnlineID != -1) {
            GameClient.IDToZombieMap.remove(zombie.OnlineID);
        }
    }

    public void clearTargetAuth(IsoPlayer player) {
        if (Core.bDebug) {
            DebugLog.log(DebugType.Multiplayer, "Clear zombies target and auth for player id=" + player.getOnlineID());
        }

        if (GameClient.bClient) {
            for (IsoZombie isoZombie : GameClient.IDToZombieMap.valueCollection()) {
                if (isoZombie.target == player) {
                    isoZombie.setTarget(null);
                }
            }
        }
    }
}
